package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.HotArticleService;
import com.heima.common.constants.article.ArticleConstants;
import com.heima.common.constants.article.HotArticleConstants;
import com.heima.common.exception.CustException;
import com.heima.feigns.AdminFeign;
import com.heima.model.admin.pojos.AdChannel;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.app.AggBehaviorDTO;
import com.heima.model.mess.app.NewBehaviorDTO;
import com.heima.utils.common.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author XHui
 * @Since 2024/3/11 16:56
 * @Version 1.0
 */
@Slf4j
@Service
public class HotArticleServiceImpl implements HotArticleService {

    @Resource
    private ApArticleMapper apArticleMapper;

    @Resource
    private AdminFeign adminFeign;

    @Resource
    private StringRedisTemplate stringRedisTemplate;


    @Override
    public void computeHotArticle() {
        // 1. 查询近5天的所有文章（已上架，未删除）
        // 1.1 获取当前日期的5天前的凌晨12:00
        String dateStr = LocalDateTime.now().minusDays(5).format(
                DateTimeFormatter.ofPattern("yyyy-MM-dd 00:00:00")
        );
        // 1.2 执行查询，获取文章数据
        List<ApArticle> articlesByDate = apArticleMapper.selectArticleByDate(dateStr);
        if (CollectionUtils.isEmpty(articlesByDate)) {
            log.info("没有查询到近5天的文章数据, 无须计算热度值");
            return;
        }

        // 2. 计算每一篇文章热度得分
        List<HotArticleVo> hotArticleVos = computeTheScoreForEachArticle(articlesByDate);
        // 2.1 根据文章热度得分降序
        /*hotArticleVos = hotArticleVos.stream()
                .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(ArticleConstants.HOT_ARTICLE_INIT_SIZE)
                .collect(Collectors.toList());*/

        // 3. 按照频道 每个频道缓存 根据热度降序 缓存前30篇文章
        cacheModerateHotArticleByChannel(hotArticleVos);

        // 4. 推荐 频道 缓存所有文章中热度最高的30条文章
        //cacheModerateHotArticle(hotArticleVos);
        sortAndCache(hotArticleVos,
                ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 从 Redis 获取行为列表
     *
     * @return list<新行为 DTO>
     */
    @Override
    public List<NewBehaviorDTO> getBehaviorListFromRedis() {
        try {
            // 1. 得到redis脚本对象
            DefaultRedisScript<List> redisScript = new DefaultRedisScript<>();
            // 1. 设置redis脚本的返回结果
            redisScript.setResultType(List.class);

            // 2. 加载redis脚本命令文件对象
            redisScript.setScriptSource(new ResourceScriptSource(
                    new ClassPathResource("redis.lua")
            ));

            // 3. 开始执行redis脚本, 并设置参数Key
            List<String> result = stringRedisTemplate.execute(redisScript,
                    Arrays.asList(HotArticleConstants.HOT_ARTICLE_SCORE_BEHAVIOR_LIST));

            // 4. 将结果转换为DTO对象
            List<NewBehaviorDTO> newBehaviorDTOs = result.stream().map(
                    string -> JSON.parseObject(string, NewBehaviorDTO.class)
            ).collect(Collectors.toList());
            return newBehaviorDTOs;

        } catch (Exception e) {
            e.printStackTrace();
            log.error("执行redis lua脚本失败, 原因：{}", e.getMessage());
            CustException.cust(AppHttpCodeEnum.SERVER_ERROR, "执行redis lua脚本失败");
        }
        return null;
    }

    @Override
    public List<AggBehaviorDTO> getAggBehaviorList(List<NewBehaviorDTO> newBehaviorDTOList) {

        List<AggBehaviorDTO> aggBehaviorList = new ArrayList<>();
        // 1. 根据List<NewBehaviorDTO>中的id首先进行分组
        Map<Long, List<NewBehaviorDTO>> collectMap = newBehaviorDTOList.stream()
                .collect(Collectors.groupingBy(NewBehaviorDTO::getArticleId));

        // 2. 统计每篇文章的点赞数、评论数、收藏数、分享数
        collectMap.forEach((articleId, messList) -> {
            Optional<AggBehaviorDTO> reduceResult = messList.stream().map(newBehaviorDTO -> {
                AggBehaviorDTO aggBehaviorDTO = new AggBehaviorDTO();
                aggBehaviorDTO.setArticleId(articleId);
                switch (newBehaviorDTO.getType()) {
                    case VIEWS:
                        aggBehaviorDTO.setView(newBehaviorDTO.getAdd());
                        break;
                    case LIKES:
                        aggBehaviorDTO.setLike(newBehaviorDTO.getAdd());
                        break;
                    case COMMENT:
                        aggBehaviorDTO.setComment(newBehaviorDTO.getAdd());
                        break;
                    case COLLECTION:
                        aggBehaviorDTO.setCollect(newBehaviorDTO.getAdd());
                        break;
                    default:
                }
                return aggBehaviorDTO;
            }).reduce((item1, item2) -> { // 汇总一篇文章的统计数据
                item1.setView(item1.getView() + item2.getView());
                item1.setLike(item1.getLike() + item2.getLike());
                item1.setComment(item1.getComment() + item2.getComment());
                item1.setCollect(item1.getCollect() + item2.getCollect());
                return item1;
            });
            if (reduceResult.isPresent()) {
                AggBehaviorDTO aggBehaviorDTO = reduceResult.get();
                log.info("热点文章 聚合计算结果  ===>{}", aggBehaviorDTO);
                aggBehaviorList.add(aggBehaviorDTO);
            }
        });

        return aggBehaviorList;
    }

    /*private void cacheModerateHotArticle(List<HotArticleVo> hotArticleVos) {
        List<HotArticleVo> hotArticleListByChannel = hotArticleVos.stream()
                .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30)
                .collect(Collectors.toList());
        sortAndCache(hotArticleListByChannel,
                ArticleConstants.HOT_ARTICLE_FIRST_PAGE + channelId);
    }*/

    private void cacheModerateHotArticleByChannel(List<HotArticleVo> hotArticleVos) {
        // 1. 远程查询频道信息
        List<AdChannel> channels = adminFeign.getAllChannels().getData();

        for (AdChannel channel : channels) {

            //  2. 获取频道id
            Integer channelId = channel.getId();

            //  3. 根据频道id，缓存文章热度得分降序的30条文章
            List<HotArticleVo> hotArticleListByChannel = hotArticleVos.stream()
                    .filter(hotArticleVo -> {
                        return Objects.equals(hotArticleVo.getChannelId(), channelId);
                    }).collect(Collectors.toList());

            sortAndCache(hotArticleListByChannel,
                    ArticleConstants.HOT_ARTICLE_FIRST_PAGE + channelId);
        }
    }

    private void sortAndCache(List<HotArticleVo> hotArticleList, String key) {
        if (CollectionUtils.isEmpty(hotArticleList)) {
            return;
        }

        // 1.  根据文章热度得分降序，并查询前30条
        hotArticleList = hotArticleList.stream().sorted(
                        Comparator.comparing(HotArticleVo::getScore).reversed()
                ).limit(ArticleConstants.HOT_ARTICLE_INIT_SIZE)
                .collect(Collectors.toList());

        // 2. 根据频道id，缓存文章热度得分降序的30条文章
        stringRedisTemplate.opsForValue()
                .set(key, JSON.toJSONString(hotArticleList));
    }

    private List<HotArticleVo> computeTheScoreForEachArticle
            (List<ApArticle> articlesByDate) {
        List<HotArticleVo> hotArticleVos = articlesByDate.stream().map(article -> {
            HotArticleVo hotArticleVo = new HotArticleVo();
            BeanUtils.copyProperties(article, hotArticleVo);
            Integer score = computeArticleScore(article);
            hotArticleVo.setScore(score);
            return hotArticleVo;
        }).collect(Collectors.toList());
        return hotArticleVos;
    }

    private Integer computeArticleScore(ApArticle article) {
        int score = 0;
        if (article.getViews() != null) {
            score += article.getViews() * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        if (article.getLikes() != null) {
            score += article.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        if (article.getComment() != null) {
            score += article.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        if (article.getCollection() != null) {
            score += article.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        return score;
    }


    /**
     * 重新计算文章分值
     *
     * @param aggBehaviorDTO
     */
    @Override
    public void updateApArticle(AggBehaviorDTO aggBehaviorDTO) {
        //1 查询文章
        ApArticle apArticle = apArticleMapper.selectById(aggBehaviorDTO.getArticleId());
        if (apArticle == null) {
            CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST);
        }

        //2 修改文章的行为数据（阅读1、点赞3、评论5、收藏8）
        if (aggBehaviorDTO.getView() != 0) {
            int view = (int) (apArticle.getViews() == null ? aggBehaviorDTO.getView() : aggBehaviorDTO.getView() + apArticle.getViews());
            apArticle.setViews(view);
        }
        if (aggBehaviorDTO.getLike() != 0) {
            int like = (int) (apArticle.getLikes() == null ? aggBehaviorDTO.getLike() : aggBehaviorDTO.getLike() + apArticle.getLikes());
            apArticle.setLikes(like);
        }
        if (aggBehaviorDTO.getComment() != 0) {
            int comment = (int) (apArticle.getComment() == null ? aggBehaviorDTO.getComment() : aggBehaviorDTO.getComment() + apArticle.getComment());
            apArticle.setComment(comment);
        }
        if (aggBehaviorDTO.getCollect() != 0) {
            int collection = (int) (apArticle.getCollection() == null ? aggBehaviorDTO.getCollect() : aggBehaviorDTO.getCollect() + apArticle.getCollection());
            apArticle.setCollection(collection);
        }

        apArticleMapper.updateById(apArticle);

        //3 计算文章分值
        Integer score = computeArticleScore(apArticle);

        // 如果是今天发布的文章，热度*3
        // 根据date对象转换为年月日字符串
        String publishStr = DateUtils.dateToString(apArticle.getPublishTime());
        String nowStr = DateUtils.dateToString(new Date());

        if (publishStr.equals(nowStr)) {
            score = score * 3;
            //当天热点数据 *3
        }
        //4 更新缓存（频道）
        updateArticleCacheToRedis(apArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());

        //5 更新推荐列表的缓存
        updateArticleCacheToRedis(apArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    /**
     * 更新Redis中的热点文章缓存数据
     *
     * @param apArticle 当前文章
     * @param score     分数
     * @param cacheKey
     */
    private void updateArticleCacheToRedis(ApArticle apArticle,
                                           Integer score,
                                           String cacheKey) {
        // 1. 查询redis中的热点文章数据
        String arrayObj = redisTemplate.opsForValue().get(cacheKey).toString();
        //String hotArticleListJsonStr =
        List<HotArticleVo> hotArticleVos = JSONArray.parseArray(
                 arrayObj,
                HotArticleVo.class);

        boolean articleExistInCache = false;

        // 2. 判断缓存数据中是否包含当前文章，若包含只需修改得分值即可
        for (HotArticleVo hotArticleVo : hotArticleVos) {
            if(hotArticleVo.getId().equals(apArticle.getId())){
                hotArticleVo.setScore(score);
                articleExistInCache = true; // 缓存命中
                break;
            }
        }

        if(!articleExistInCache){// 3. 缓存中不包含当前文章，则新增
            HotArticleVo hotArticleVo = new HotArticleVo();
            BeanUtils.copyProperties(apArticle, hotArticleVo);
            hotArticleVo.setScore(score);
            hotArticleVos.add(hotArticleVo);
        }

        // 4. 缓存数据排序，并截取前30条数据
        hotArticleVos = hotArticleVos.stream()
                .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(ArticleConstants.HOT_ARTICLE_INIT_SIZE)
                .collect(Collectors.toList());

        // 5. 将排序后的数据再次写入redis缓存
        stringRedisTemplate.opsForValue().set(cacheKey, JSONArray.toJSONString(hotArticleVos));
    }

    @Transactional
    @Override
    public void batchUpdateApArticles(List<AggBehaviorDTO> aggBehaviors) {
        aggBehaviors.forEach(this::updateApArticle);
    }
}


